/**
 * @author Ed Spencer
 */
Ext.define('Ext.ux.DataView.DragSelector', {
	requires : ['Ext.dd.DragTracker', 'Ext.util.Region'],

	/**
	 * Initializes the plugin by setting up the drag tracker
	 */
	init : function(dataview) {
		/**
		 * @property dataview
		 * @type Ext.view.View The DataView bound to this instance
		 */
		this.dataview = dataview;
		dataview.mon(dataview, {
					beforecontainerclick : this.cancelClick,
					scope : this,
					render : {
						fn : this.onRender,
						scope : this,
						single : true
					}
				});
	},

	/**
	 * @private Called when the attached DataView is rendered. This sets up the
	 *          DragTracker instance that will be used to created a dragged
	 *          selection area
	 */
	onRender : function() {
		/**
		 * @property tracker
		 * @type Ext.dd.DragTracker The DragTracker attached to this instance.
		 *       Note that the 4 on* functions are called in the scope of the
		 *       DragTracker ('this' refers to the DragTracker inside those
		 *       functions), so we pass a reference to the DragSelector so that
		 *       we can call this class's functions.
		 */
		this.tracker = Ext.create('Ext.dd.DragTracker', {
					dataview : this.dataview,
					el : this.dataview.el,
					dragSelector : this,
					onBeforeStart : this.onBeforeStart,
					onStart : this.onStart,
					onDrag : this.onDrag,
					onEnd : this.onEnd
				});

		/**
		 * @property dragRegion
		 * @type Ext.util.Region Represents the region currently dragged out by
		 *       the user. This is used to figure out which dataview nodes are
		 *       in the selected area and to set the size of the Proxy element
		 *       used to highlight the current drag area
		 */
		this.dragRegion = Ext.create('Ext.util.Region');
	},

	/**
	 * @private Listener attached to the DragTracker's onBeforeStart event.
	 *          Returns false if the drag didn't start within the DataView's el
	 */
	onBeforeStart : function(e) {
		return e.target == this.dataview.getEl().dom;
	},

	/**
	 * @private Listener attached to the DragTracker's onStart event. Cancel's
	 *          the DataView's containerclick event from firing and sets the
	 *          start co-ordinates of the Proxy element. Clears any existing
	 *          DataView selection
	 * @param {Ext.EventObject}
	 *            e The click event
	 */
	onStart : function(e) {
		var dragSelector = this.dragSelector, dataview = this.dataview;

		// Flag which controls whether the cancelClick method vetoes the
		// processing of the DataView's containerclick event.
		// On IE (where else), this needs to remain set for a millisecond after
		// mouseup because even though the mouse has
		// moved, the mouseup will still trigger a click event.
		this.dragging = true;

		// here we reset and show the selection proxy element and cache the
		// regions each item in the dataview take up
		dragSelector.fillRegions();
		dragSelector.getProxy().show();
		dataview.getSelectionModel().deselectAll();
	},

	/**
	 * @private Reusable handler that's used to cancel the container click event
	 *          when dragging on the dataview. See onStart for details
	 */
	cancelClick : function() {
		return !this.tracker.dragging;
	},

	/**
	 * @private Listener attached to the DragTracker's onDrag event. Figures out
	 *          how large the drag selection area should be and updates the
	 *          proxy element's size to match. Then iterates over all of the
	 *          rendered items and marks them selected if the drag region
	 *          touches them
	 * @param {Ext.EventObject}
	 *            e The drag event
	 */
	onDrag : function(e) {
		var dragSelector = this.dragSelector, selModel = dragSelector.dataview
				.getSelectionModel(), dragRegion = dragSelector.dragRegion, bodyRegion = dragSelector.bodyRegion, proxy = dragSelector
				.getProxy(), regions = dragSelector.regions, length = regions.length,

		startXY = this.startXY, currentXY = this.getXY(), minX = Math.min(
				startXY[0], currentXY[0]), minY = Math.min(startXY[1],
				currentXY[1]), width = Math.abs(startXY[0] - currentXY[0]), height = Math
				.abs(startXY[1] - currentXY[1]), region, selected, i;

		Ext.apply(dragRegion, {
					top : minY,
					left : minX,
					right : minX + width,
					bottom : minY + height
				});

		dragRegion.constrainTo(bodyRegion);
		proxy.setRegion(dragRegion);

		for (i = 0; i < length; i++) {
			region = regions[i];
			selected = dragRegion.intersect(region);

			if (selected) {
				selModel.select(i, true);
			} else {
				selModel.deselect(i);
			}
		}
	},

	/**
	 * @private Listener attached to the DragTracker's onEnd event. This is a
	 *          delayed function which executes 1 millisecond after it has been
	 *          called. This is because the dragging flag must remain active to
	 *          cancel the containerclick event which the mouseup event will
	 *          trigger.
	 * @param {Ext.EventObject}
	 *            e The event object
	 */
	onEnd : Ext.Function.createDelayed(function(e) {
		var dataview = this.dataview, selModel = dataview.getSelectionModel(), dragSelector = this.dragSelector;

		this.dragging = false;
		dragSelector.getProxy().hide();
	}, 1),

	/**
	 * @private Creates a Proxy element that will be used to highlight the drag
	 *          selection region
	 * @return {Ext.Element} The Proxy element
	 */
	getProxy : function() {
		if (!this.proxy) {
			this.proxy = this.dataview.getEl().createChild({
						tag : 'div',
						cls : 'x-view-selector'
					});
		}
		return this.proxy;
	},

	/**
	 * @private Gets the region taken up by each rendered node in the DataView.
	 *          We use these regions to figure out which nodes to select based
	 *          on the selector region the user has dragged out
	 */
	fillRegions : function() {
		var dataview = this.dataview, regions = this.regions = [];

		dataview.all.each(function(node) {
					regions.push(node.getRegion());
				});
		this.bodyRegion = dataview.getEl().getRegion();
	}
});